package birdsong.comm;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import javax.swing.JOptionPane;

import birdsong.comm.ServerSideMessage.Type;

/**
 * Communication system for {@link birdsong.client.BirdsongClient Birdsong clients}. Birdsong clients must 
 * communicate with a Birdsong server exclusively through this class. 
 * 
 * @author While True
 * @version 1
 */

public class ClientCommunicator implements ClientComm {

        private BlockingQueue<ClientMessage> clientReceiverMessageList = new LinkedBlockingQueue<ClientMessage>();
        private BlockingQueue<ServerMessage> clientSenderMessageList = new LinkedBlockingQueue<ServerMessage>();

        private boolean isConnected = false;
        private Socket socket;
        private DealWithServer dealWithServer;
        private String clientNickname;
        private static final int SERVER_PORT = 8080;
        
        /**
         * Establish a connection to a Birdsong server. This method is the first one called by 
         * the client as no other operation (expect for {@link #isConnected()}) can be performed before
         * a connection to the server has been established.
         * 
         * @param host                    Address of the server. For instance "localhost", "192.168.1.1", and so on.
         * @param nickName                The nickname to use for the client.
         * @throws UnknownHostException   Thrown if the host cannot be found.
         * @throws IOException            Thrown if a connection cannot be established.
         */
        @Override
        public void connect(String serverAddress, String nickName)      throws UnknownHostException, IOException {
          if(!isConnected){
                        clientNickname = nickName;
                        InetAddress serverAdd = InetAddress.getByName(serverAddress);
                        socket = new Socket(serverAdd, SERVER_PORT);
                        isConnected = true;
                        dealWithServer = new DealWithServer(socket, clientNickname, this);
                        dealWithServer.start(); 
          }
          else 
                  JOptionPane.showMessageDialog(null, "CLIENTCOMMUNICATOR INFO: this client is already connected to the server.");
        } 

        /** 
         * Check if currently connected to a host.
         * 
         * @return <b>true</b> if currently connected to a host, <b>false</b> otherwise.
         */
        @Override
        public boolean isConnected() {
                return isConnected;
        }

        /**
         * Disconnect from a Birdsong server. Any sockets are closed immediately. 
         * Pending messages are not guaranteed to be delivered before disconnecting.
         */     
        @Override
        public void disconnect() {
                                
                ServerMessage clientDisconnectedMessage = new ServerMessage(Type.CLIENT_DISCONNECTED, null, clientNickname);
                getClientSenderMessageList().clear();
                getClientSenderMessageList().add(clientDisconnectedMessage);
                for (Thread t : dealWithServer.getActiveThreads()) {
                        try { 
                                t.join();
                                isConnected = false;
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                        
                }
        }
        
        /**
         * Get the next message received from the server. If no message 
         * has been received, the method blocks until a message has 
         * been received. If no message will ever be received (connection is down), null is returned.
         * 
         * @return The next message sent by the server to the client or null if no message will ever be received (disconnected from server).
         */
        @Override
        public ClientSideMessage getNextMessage() {
                ClientMessage nextMessage = null;
                try { 
                        nextMessage = getClientReceiverMessageList().take();
                } catch (InterruptedException e) {}
                return nextMessage;
        }

        /** 
         * Check if a message from the server is pending. If {@link #hasNextMessage()} returns <b>true</b>, a call to {@link #getNextMessage()} 
         * will return immediately with the oldest message in the queue. If {@link #hasNextMessage()} returns <b>false</b>, a call to {@link #getNextMessage()} 
         * will block until a message has been received.
         * 
         * @return <b>true</b> if a message from the server is currently pending, otherwise <b>false</b>.
         */
        @Override
        public boolean hasNextMessage() {
                return !getClientReceiverMessageList().isEmpty();
        }

        /**
         * Send a Birdsong message from this client to the server which should then relay the message to all followers.
         * 
         * @param birdsongMessage The Birdsong message to be sent.
         */
        @Override
        public void sendBirdsongMessage(String payload) {
                ServerMessage birdsongMessage = new ServerMessage(Type.BIRDSONG_MESSAGE, payload, clientNickname);
                getClientSenderMessageList().add(birdsongMessage);
        }

        /**
         * Request to follow another client.
         * 
         * @param nickname the nickname of the client that should be followed.
         */
        @Override
        public void follow(String nickname) {
                ServerMessage followMessage = new ServerMessage(Type.FOLLOW, nickname, clientNickname);
                getClientSenderMessageList().add(followMessage);
        }
        
        /**
         * Request to unfollow a client.
         * 
         * @param nickname the nickname of the client currently followed that should be unfollowed.
         */
        @Override
        public void unfollow(String nickname) {
                ServerMessage unfollowMessage = new ServerMessage(Type.UNFOLLOW,nickname, clientNickname);
                getClientSenderMessageList().add(unfollowMessage);

        }

        /** Getter that returns the list which contains all the messages of the client received from the Birdsong server.
         * 
         * @return Returns the list with the messages received from the server.
         */
        public BlockingQueue<ClientMessage> getClientReceiverMessageList() {
                return clientReceiverMessageList;
        }

        /** Getter that returns the list which contains all the messages of the client sent to the Birdsong server.
         * 
         * @return Returns the list with the messages sent to the Birdsong server.
         */
        public BlockingQueue<ServerMessage> getClientSenderMessageList() {
                return clientSenderMessageList;
        }
}